/*
 * DirOperations.cpp
 *
 *  Created on: Apr 8, 2010
 *      Author: Lukasz Spintzyk
 */


#include "DirOperations.h"
#include "OtherFsOperations.h"
#include "Filesystem.h"
#include "InodeReader.h"
#include "DirectoryReader.h"
#include "FSException.h"
#include "utils.h"


namespace CudaCryptoFs {


int readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
		struct fuse_file_info *fi)
{

	try{
		DirectoryReader* dr = getFS()->getDirectoryReader();
		boost::shared_ptr<Inode> inode = dr->path2Inode(path);
		assert(inode->isDir());

		boost::shared_ptr<DirectoryIterator> it = dr->begin(inode);

		fill_with_direntries(it, buf, filler);

		return 0;
	}
	catch(FSException& e)
	{
		return -e.code;
	}

}


void fill_with_direntries(boost::shared_ptr<DirectoryIterator> it, void* buf, fuse_fill_dir_t filler)
{
	while (!it->end())
	{
		const DirEntry& e = **it;
		int ret = filler(buf, e.d_name.c_str(), NULL, 0); // TODO it is worth to read desc ?
		assert(ret!=1);
		++(*it);
	}
}

/*****************************************************************************/
/*****************************************************************************/

int getdir(const char *, fuse_dirh_t, fuse_dirfil_t)
{
	// TODO write an plug, maybe it is not necessary because of readdir
	return -EACCES;
}

/*****************************************************************************/
/*****************************************************************************/

int mkdir(const char *newDir, mode_t mode)
{
	// TODO write an plug
	try
	{
		Filesystem* fs = getFS();
		InodeManager* iMgr = fs->getInodeReader();
		DirectoryReader* dr = fs->getDirectoryReader();

		InodePtr parent_dir = dr->path2Inode(getParentDir(newDir));


		InodePtr new_dir = iMgr->createNewInode();
		new_dir->initDirInode(fuse_get_context(), mode);
		iMgr->reserveBlocksForInode(new_dir, 1);

		dr->setUpDirectory(new_dir, parent_dir->d_num);

		iMgr->setInode(new_dir->d_num, new_dir);

		DirEntry e(findLastName(newDir), new_dir->d_num, EXT2_FT_DIR);

		dr->addDirEntry(e, parent_dir);

		iMgr->setInode(parent_dir->d_num, parent_dir);
	}
	catch(FSException& e)
	{
		return -e.code;
	}


	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int rmdir(const char * path)
{
	// TODO write an plug
	try{
		Filesystem* fs = getFS();
		InodeManager* ir = fs->getInodeReader();
		DirectoryReader* dr = fs->getDirectoryReader();

		std::string dir_to_del = findLastName(path);

		InodePtr parent_inode = dr->path2Inode(getParentDir(path));
		InodePtr dir = dr->path2Inode(path);
		assert(dir->isDir());
		assert(parent_inode->isDir());

		boost::shared_ptr<DirectoryIterator> it = dr->begin(parent_inode);
		remove_dir_entry_from_directory(it, dir_to_del);
		ir->decrementLinksCount( dir );
	}
	catch (FSException& e)
	{
		return -e.code;
	}
	return 0;
}

void remove_dir_entry_from_directory(boost::shared_ptr<DirectoryIterator> it,
									const std::string& dir_to_del)
{
	while (!it->end()) {
		const DirEntry& e = **it;
		if (e.d_name == dir_to_del) {
			it->removeEntry();
			break;
		}
		++(*it);
	}
}

/*****************************************************************************/
/*****************************************************************************/

int rename(const char *src, const char *dest)
{
	// TODO write and plug
	Filesystem* fs = getFS();
	InodeManager* iMgr = fs->getInodeReader();
	DirectoryReader* dMgr = fs->getDirectoryReader();

	std::string parent_src_dir = getParentDir(src);
	std::string src_name = findLastName(src);

	std::string parent_dest_dir = getParentDir(dest);
	std::string dest_name = findLastName(dest);
	boost::shared_ptr<Inode> parent_src_inode;
	boost::shared_ptr<Inode> dest_parent_inode;

	try
	{
		fs->getFileContainer()->renameFile(src, dest);
		parent_src_inode = dMgr->path2Inode(parent_src_dir);
		dest_parent_inode = dMgr->path2Inode(parent_dest_dir);
	}
	catch (FSException& e)
	{
		return -e.code;
	}


	boost::shared_ptr<DirectoryIterator> it = dMgr->begin(parent_src_inode);

	while(!it->end())
	{
		DirEntry e = **it;

		if (e.d_name == src_name)
		{
			it->removeEntry();
			e.d_name = dest_name;

			dMgr->addDirEntry(e, dest_parent_inode);
			break;
		}
		++(*it);
	}


	iMgr->setInode(parent_src_inode->d_num, parent_src_inode);
	iMgr->setInode(dest_parent_inode->d_num, dest_parent_inode);

	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int opendir(const char *path, struct fuse_file_info *ffi)
{
	// TODO write an plug
	try
	{
		Filesystem* fs = getFS();
		DirectoryReader* dr = fs->getDirectoryReader();
		boost::shared_ptr<Inode> inode = dr->path2Inode(path);

		struct fuse_context* context = fuse_get_context();
		uint16_t uid = context->uid;
		uint16_t gid = context->gid;

		assert(inode->isDir());
		if (read_ok(inode, uid, gid))
		{
			return 0;
		}

		return -EACCES;
	}
	catch(FSException& e)
	{
		return -e.code;
	}


}

/*****************************************************************************/
/*****************************************************************************/

int releasedir(const char *, struct fuse_file_info *)
{
	// TODO write an plug
	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int fsyncdir(const char *, int, struct fuse_file_info *)
{
	// TODO write an plug
		return -ENOENT;
}

} //end namespace CudaCryptoFs

